The ONE是基于代理离散事件(agent-based discrete event)仿真器。把节点间连接建立UP或DOWN,消息产生、发送、接收都视为事件,The ONE进行一些初始化工作后,反反复复处理这些动态事件,这就是The ONE运行原理,这点非常类似于Contiki(机制是相通的)。因此,理清The ONE的main函数和事件处理,就能对The ONE有个大局观,日后编程,思路也更加清晰。本文深入源码剖析The ONE的主函数。
1. main概述
The ONE有两种运行模式,即TEXT和GUI,以TEXT为例,命令行运行方式如下:
./one.sh –b 5 default_settings.txt
以批处理(batch mode)为例,main函数整个调用过程如下:
main(String[] args) //DTNSim.java
initSettings(confFiles, firstConfIndex); //初始化设置,读入default_settings.txt
new DTNSimTextUI().start();
initModel(); //创建仿真模型
runSim(); //开始仿真
world.update(); //在每个updateInterval,处理所有到期事件
首先,解析运行命令,将运行次数5保存在nrofRuns
;initSettings()
将配置文件default_settings.txt
读到static Properties props
。在这里,直观理解成将txt文件读到内存,方便后续读取。
其次,initModel()
创建仿真模型。先基于配置文件创建场景(SimScenario.getInstance
,创建DTNHost
,设置World
);增加reports
,处理warmupTime
(clockTime -= warmupTime
, warmupTime
在配置文件设置),对移动模型热身warmupMovementModel()
。
最后,rumSim()
开始仿真。执行更新world.update()
,努力(在updateInterval
内)处理所有事件,更新节点移动moveHosts
,更新节点连接情况updateHosts
。
2. 相关源代码
The ONE有两种运行模式,即TEXT和GUI,以TEXT为例,命令行运行方式如下:
./one.sh –b 5 default_settings.txt
-b
指批处理模式(batch mode),5
指运行次数(nrofRuns[2]
,实际上是一个范围),default_settings.txt
是配置文件。
2.1 main函数
main
函数首先解析命令行参数,读入初始设置文件default_settings.txt
;接着创建仿真实例,创建仿真模型;最后开始仿真。去掉一些不相关的代码,main
函数源代码(core/DTNSim.java
)如下:
public static void main(String[] args) {
nrofRuns = parseNrofRuns(args[1]); //解析命令行参数,将运行次数保存在nrofRuns[2],配置文件名保存在confFiles[]
initSettings(confFiles, firstConfIndex); //confFiles在这里是default_settings.txt
for (int i=nrofRuns[0]; i<nrofRuns[1]; i++) {
Settings.setRunIndex(i);
resetForNextRun();
new DTNSimTextUI().start(); //TextUI
}
}
new DTNSimTextUI()
得到一个场景实例,调用start()
,该函数仅包含调用另两个函数:initModel()
和runSim()
。源代码如下:
//DTNSim.java中new DTNSimTextUI().start();
//DTNSimTextUI.java
public void start() {
initModel(); //增加reports, 设置仿真时间(减去warmupTime)
runSim(); //抽象函数,定位到DTNSimTextUI.runSim()
}
2.2 初始化模型initModel
初始化模型initModel
主要做了两件事:其一,增加报告reports
;其二,处理热身时间warmup
。相关源代码如下:
//DTNSimUI.java 初始化模型
protected SimScenario scen;
private void initModel() {
settings = new Settings();
this.scen = SimScenario.getInstance(); //创建场景,包括创建DTNHost
/*** 增加报告, add reports ***/
protected Vector<Report> reports; //
String reportClass = settings.getSetting(REPORT_S + i); //REPORT_S="Report.report",想想配置文件
addReport((Report)settings.createObject(REPORT_PAC + reportClass)); //REPORT_PAC="report.", 包名前缀
/*** 处理热身,即不把热身这段时间的仿真统计信息计算在内 ***/
SimClock c = SimClock.getInstance();
c.setTime(-warmupTime);
//移动模型热身
protected World world; //包含所有节点,更新节点的位置和连接状态
this.world = this.scen.getWorld();
world.warmupMovementModel(warmupTime);
}
2.3 开始仿真rumSim
rumSim
最主要的是调用world.update()
,其主要源代码如下:
//DTNSimTextUI.java
protected void runSim() {
double simTime = SimClock.getTime();
double endTime = scen.getEndTime();
while (simTime < endTime && !simCancelled) {
world.update();
simTime = SimClock.getTime();
this.update(false); //如果下一个updateInterval还没到,就什么都不做
}
/*** 整个仿真结束 ***/
simDone = true;
done(); //所有Report r.close()
this.update(true); //更新时间统计值
}
2.4 world.update
world.update()
处理所有到期的事件。获取含有到期事件的事件队列,处理到期的事件。主要源代码如下:
//World.java
private List<EventQueue> eventQueues; //事件队列链表,用链表将事件队列组织起来
public void update () {
double runUntil = SimClock.getTime() + this.updateInterval; //
/* process all events that are due until next interval update */
setNextEventQueue(); //找到一个事件队列,该事件队列含有本updateInterval可以处理的事件
while (this.nextQueueEventTime <= runUntil) {
simClock.setTime(this.nextQueueEventTime);
ExternalEvent ee = this.nextEventQueue.nextEvent(); //取得事件
ee.processEvent(this); //处理事件
updateHosts(); // update all hosts after every event
setNextEventQueue();
}
moveHosts(this.updateInterval); //Moves all hosts in the world for a given amount of time
simClock.setTime(runUntil);
updateHosts();
}